// 版权归Go作者所有。版权所有。
// 此源代码的使用受BSD样式
// 许可证的约束，该许可证可以在许可证文件中找到。

package net

import (
	"context"
	"syscall"
)

// 错误（mikio）：在每个POSIX平台上，从“ip4”网络读取
// 使用ReadFrom或ReadFromIP方法可能不会返回完整的
// IPv4数据包，包括其头，即使有可用的空间
// 也是如此。即使在Read或ReadMsgIP 
// 可能返回完整数据包的情况下，也会发生这种情况。因此，如果接收完整数据包非常重要，建议不要使用这些方法。
// 
// Go 1兼容性指南使我们无法改变这些方法的行为；使用Read或ReadMsgIP 
// 代替。

// 错误（mikio）：在JS和Plan 9上，与IPConn相关的方法和函数未实现。

// 错误（mikio）：在Windows上，IPConn的文件方法未实现
// 错误。

// IPAddr表示IP端点的地址。
type IPAddr struct {
	IP   IP
	Zone string // IPv6作用域寻址区域
}

// 网络返回地址的网络名称“ip”。
func (a *IPAddr) Network() string { return "ip" }

func (a *IPAddr) String() string {
	if a == nil {
		return "<nil>"
	}
	ip := ipEmptyString(a.IP)
	if a.Zone != "" {
		return ip + "%" + a.Zone
	}
	return ip
}

func (a *IPAddr) isWildcard() bool {
	if a == nil || a.IP == nil {
		return true
	}
	return a.IP.IsUnspecified()
}

func (a *IPAddr) opAddr() Addr {
	if a == nil {
		return nil
	}
	return a
}

// ResolveIPAddress返回IP端点的地址。
// 
// 网络必须是IP网络名称。
// 
// 如果address参数中的主机不是文字IP地址，
// ResolveIPAddress将地址解析为IP端点的地址。
// 否则，它将地址解析为文本IP地址。
// address参数可以使用主机名，但不建议使用此参数，因为它最多会返回主机名的一个
// IP地址。
// 
// 有关网络和地址的说明，请参阅func Dial 
// 参数。
func ResolveIPAddr(network, address string) (*IPAddr, error) {
	if network == "" { // Go 1.0未记录行为的提示通配符
		network = "ip"
	}
	afnet, _, err := parseNetwork(context.Background(), network, false)
	if err != nil {
		return nil, err
	}
	switch afnet {
	case "ip", "ip4", "ip6":
	default:
		return nil, UnknownNetworkError(network)
	}
	addrs, err := DefaultResolver.internetAddrList(context.Background(), afnet, address)
	if err != nil {
		return nil, err
	}
	return addrs.forResolve(network, address).(*IPAddr), nil
}

// IPConn是用于IP网络连接的Conn和PacketConn接口
// 的实现。
type IPConn struct {
	conn
}

// SyscallConn返回原始网络连接。
// 这实现了syscall.Conn接口。
func (c *IPConn) SyscallConn() (syscall.RawConn, error) {
	if !c.ok() {
		return nil, syscall.EINVAL
	}
	return newRawConn(c.fd)
}

// ReadFromIP的行为类似于ReadFrom，但返回一个IPAddr。
func (c *IPConn) ReadFromIP(b []byte) (int, *IPAddr, error) {
	if !c.ok() {
		return 0, nil, syscall.EINVAL
	}
	n, addr, err := c.readFrom(b)
	if err != nil {
		err = &OpError{Op: "read", Net: c.fd.net, Source: c.fd.laddr, Addr: c.fd.raddr, Err: err}
	}
	return n, addr, err
}

// ReadFrom实现PacketConn ReadFrom方法。
func (c *IPConn) ReadFrom(b []byte) (int, Addr, error) {
	if !c.ok() {
		return 0, nil, syscall.EINVAL
	}
	n, addr, err := c.readFrom(b)
	if err != nil {
		err = &OpError{Op: "read", Net: c.fd.net, Source: c.fd.laddr, Addr: c.fd.raddr, Err: err}
	}
	if addr == nil {
		return n, nil, err
	}
	return n, addr, err
}

// ReadMsgIP从c读取一条消息，将有效负载复制到b中，并将相关带外数据复制到oob中。它返回复制到b中的
// 字节数、复制到oob中的字节数、在消息上设置的标志
// 以及消息的源地址。
// 
// golang.org/x/net/ipv4和golang.org/x/net/ipv6包可以是
// 用于操作oob中的IP级别套接字选项。
func (c *IPConn) ReadMsgIP(b, oob []byte) (n, oobn, flags int, addr *IPAddr, err error) {
	if !c.ok() {
		return 0, 0, 0, nil, syscall.EINVAL
	}
	n, oobn, flags, addr, err = c.readMsg(b, oob)
	if err != nil {
		err = &OpError{Op: "read", Net: c.fd.net, Source: c.fd.laddr, Addr: c.fd.raddr, Err: err}
	}
	return
}

// WriteToIP的行为类似于WriteTo，但采用IPAddress。
func (c *IPConn) WriteToIP(b []byte, addr *IPAddr) (int, error) {
	if !c.ok() {
		return 0, syscall.EINVAL
	}
	n, err := c.writeTo(b, addr)
	if err != nil {
		err = &OpError{Op: "write", Net: c.fd.net, Source: c.fd.laddr, Addr: addr.opAddr(), Err: err}
	}
	return n, err
}

// WriteTo实现PacketConn WriteTo方法。
func (c *IPConn) WriteTo(b []byte, addr Addr) (int, error) {
	if !c.ok() {
		return 0, syscall.EINVAL
	}
	a, ok := addr.(*IPAddr)
	if !ok {
		return 0, &OpError{Op: "write", Net: c.fd.net, Source: c.fd.laddr, Addr: addr, Err: syscall.EINVAL}
	}
	n, err := c.writeTo(b, a)
	if err != nil {
		err = &OpError{Op: "write", Net: c.fd.net, Source: c.fd.laddr, Addr: a.opAddr(), Err: err}
	}
	return n, err
}

// WriteMsgIP通过c将消息写入addr，从
// b复制有效负载，并从oob复制相关带外数据。它返回
// 写入的有效负载和带外字节数。
// 
// golang.org/x/net/ipv4和golang.org/x/net/ipv6包可以是
// 用于操作oob中的IP级别套接字选项。
func (c *IPConn) WriteMsgIP(b, oob []byte, addr *IPAddr) (n, oobn int, err error) {
	if !c.ok() {
		return 0, 0, syscall.EINVAL
	}
	n, oobn, err = c.writeMsg(b, oob, addr)
	if err != nil {
		err = &OpError{Op: "write", Net: c.fd.net, Source: c.fd.laddr, Addr: addr.opAddr(), Err: err}
	}
	return
}

func newIPConn(fd *netFD) *IPConn { return &IPConn{conn{fd}} }

// DialIP的作用类似于IP网络的拨号。
// 
// 网络必须是IP网络名；有关详细信息，请参阅func拨号。
// 
// 如果laddr为零，则自动选择本地地址。
// 如果raddr的IP字段为零或未指定的IP地址，则假定为
// 本地系统。
func DialIP(network string, laddr, raddr *IPAddr) (*IPConn, error) {
	if raddr == nil {
		return nil, &OpError{Op: "dial", Net: network, Source: laddr.opAddr(), Addr: nil, Err: errMissingAddress}
	}
	sd := &sysDialer{network: network, address: raddr.String()}
	c, err := sd.dialIP(context.Background(), laddr, raddr)
	if err != nil {
		return nil, &OpError{Op: "dial", Net: network, Source: laddr.opAddr(), Addr: raddr.opAddr(), Err: err}
	}
	return c, nil
}

// ListenIP的作用类似于IP网络的ListenPacket。
// 
// 网络必须是IP网络名；有关详细信息，请参阅func拨号。
// 
// 如果laddr的IP字段为零或未指定的IP地址，
// ListenIP将侦听本地系统的所有可用IP地址
// 多播IP地址除外。
func ListenIP(network string, laddr *IPAddr) (*IPConn, error) {
	if laddr == nil {
		laddr = &IPAddr{}
	}
	sl := &sysListener{network: network, address: laddr.String()}
	c, err := sl.listenIP(context.Background(), laddr)
	if err != nil {
		return nil, &OpError{Op: "listen", Net: network, Source: nil, Addr: laddr.opAddr(), Err: err}
	}
	return c, nil
}
